/*
 * Copyright (c) 2007 Team and Concepts Limited.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Team and Concepts Limited nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TEAM AND CONCEPTS LIMITED ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL TEAM AND CONCEPTS LIMITED BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.editgrid.api.client;

import java.io.*;
import java.util.*;
import org.apache.commons.httpclient.*;
import org.xml.sax.SAXException;

/**
 * An EditGrid API 2.0 service client.
 */
public class EditGridService {
	private String appId;
	private String apiToken;
	private URI baseUri;
	static protected Properties properties;
	
	static {
		InputStream is = EditGridService.class.getResourceAsStream("setting.properties");
		properties = new Properties();
		try {
			properties.load(is);
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}
	
	/**
	 * Returns the Application ID.
	 * @return The <code>appId</code> that was passed into the
	 * <code>EditGridService</code> constructor
	 */
	public String getAppId() {
		return this.appId;
	}
	
	/**
	 * Returns the API Token.
	 * @return The API Token obtained from EditGrid API 2.0 service. The
	 * value is a null reference if <code>authenticate</code> is not
	 * successfully called.
	 */
	public String getApiToken() {
		return this.apiToken;
	}
	
	/**
	 * Returns the URI of EditGrid installation.
	 * @return An <code>URI</code> object.
	 */
	public URI getBaseUri() {
		return this.baseUri;
	}
	
	/**
	 * Initializes a new instance of the <code>EditGridService</code> class.
	 * @param appId A valid Application ID obtain from EditGrid.
	 * @param baseUri An <code>URI</code> class than contains the address of
     * EditGrid installation.
	 */
	public EditGridService(String appId, URI baseUri) {
		this.appId = appId;
		this.baseUri = baseUri;
	}
	
	/**
	 * Initializes a new instance of the <code>EditGridService</code> class
	 * that connects to the default EditGrid installation.
	 * @param appId A valid Application ID obtain from EditGrid.
	 */
	public EditGridService(String appId) {
		this(appId, (URI) null);
		try {
			this.baseUri = new URI(properties.getProperty("DefaultEditGridURL"), true);
		} catch (URIException e) {
			throw new IllegalStateException("The client library is not configured properly");
		}
	}
	
	/**
	 * Obtain an API Token from EditGrid API 2.0 service, without user authentication.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public void authenticate() throws HttpException, IOException, EditGridServiceException {
		this.apiToken = Authentication.getToken(this.baseUri, this.appId);
	}
	
	/**
	 * Obtain an API Token from EditGrid API 2.0 service, using password authentication.
	 * @param login Login name of the user.
	 * @param password Password of the user.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public void authenticate(String login, String password)
			throws HttpException, IOException, EditGridServiceException {
		this.apiToken = Authentication.getToken(this.baseUri, this.appId, login, password);
	}
	
	/**
	 * Obtain an API Token from EditGrid API 2.0 service, using password authentication.
	 * @param organisation Organisation of the user.
	 * @param login Login name of the user.
	 * @param password Password of the user.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public void authenticate(String organisation, String login, String password)
			throws HttpException, IOException, EditGridServiceException {
		this.apiToken = Authentication.getToken(this.baseUri, this.appId, organisation, login, password);
	}
	
	/**
	 * Revoke the API Token obtained previously.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public void unauthenticate() throws HttpException, IOException, EditGridServiceException {
		if (this.apiToken != null) {
			Authentication.RevokeToken(this.baseUri, this.appId, this.apiToken);
		}
	}
	
	/**
	 * Returns an instance of <code>User</code> class containing the metadata
	 * of the authenticated user.
	 * @return An <code>User</code> object.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public User getAuthenticatedUser()
			throws HttpException, IOException, SAXException, EditGridServiceException {
		AtomEntry entry = this.queryEntry("users/_me");
		return new User(entry);
	}
	
	/**
	 * Create resource request.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param entry The <code>AtomEntry</code> to be created.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource created.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry create(String path, AtomEntry entry)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		return this.create(path, entry, new QueryOption());
	}
	
	/**
	 * Create resource request with <code>QueryOption</code>.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param entry The <code>AtomEntry</code> to be created.
	 * @param option The query options of the request.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource created.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry create(String path, AtomEntry entry, QueryOption option)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		URI u = new URI(this.baseUri, "rest/1/" + path, true);
		u.setQuery(option.toString());
		EditGridHttpRequest req = new EditGridHttpRequest("POST", u, this.appId);
		req.setApiToken(apiToken);
		req.setRequestBody(entry.toAtomAsString());
		req.setContentType("application/atom+xml");
		EditGridHttpResponse res = req.getResponse();
		if (res.getStatusCode() == 201) {
			return res.getResponseAtomEntry();
		} else {
			throw new EditGridServiceException(res.getResponseText());
		}
	}
	
	/**
	 * Update resource request.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param entry The <code>AtomEntry</code> used to update an existing resource.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource updated
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry update(String path, AtomEntry entry) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		return this.update(path, entry, new QueryOption());
	}
	
	/**
	 * Update resource request with <code>QueryOption</code>.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param entry The <code>AtomEntry</code> used to update an existing resource.
	 * @param option Request path without the "rest/1/" prefix.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource updated.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry update(String path, AtomEntry entry, QueryOption option) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		URI u = new URI(this.baseUri, "rest/1/" + path, true);
		u.setQuery(option.toString());
		EditGridHttpRequest req = new EditGridHttpRequest("PUT", u, this.appId);
		req.setApiToken(apiToken);
		req.setRequestBody(entry.toAtomAsString());
		req.setContentType("application/atom+xml");
		EditGridHttpResponse res = req.getResponse();
		if (res.getStatusCode() == 200) {
			return res.getResponseAtomEntry();
		} else {
			throw new EditGridServiceException(res.getResponseText());
		}
	}

	/**
	 * Retrieve resource request.
	 * @param path Request path without the "rest/1/" prefix.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource requested
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry queryEntry(String path) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		return this.queryEntry(path, new QueryOption());
	}
	
	/**
	 * Retrieve resource request with <code>QueryOption</code>.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param option The query options of the request.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource requested.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry queryEntry(String path, QueryOption option) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		URI u = new URI(this.baseUri, "rest/1/" + path, true);
		u.setQuery(option.toString());
		EditGridHttpRequest req = new EditGridHttpRequest("GET", u, this.appId);
		req.setApiToken(this.apiToken);
		EditGridHttpResponse res = req.getResponse();
		if (res.getStatusCode() == 200) {
			return res.getResponseAtomEntry();
		} else {
			throw new EditGridServiceException(res.getResponseText());
		}
	}
	
	/**
	 * Retrieve resource list request.
	 * @param path Request path without the "rest/1/" prefix.
	 * @return A list of <code>AtomEntry</code> objects contain metadata of the resouce requested.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomFeed query(String path) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		return this.query(path, new QueryOption());
	}
	
	/**
	 * Retrieve resource list request with <code>QueryOption</code>.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param option The query options of the request.
	 * @return A list of <code>AtomEntry</code> objects contain metadata of the resouce requested.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomFeed query(String path, QueryOption option)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		URI u = new URI(this.baseUri, "rest/1/" + path, true);
		u.setQuery(option.toString());
		EditGridHttpRequest req = new EditGridHttpRequest("GET", u, this.appId);
		req.setApiToken(this.apiToken);
		EditGridHttpResponse res = req.getResponse();
		if (res.getStatusCode() == 200) {
			return res.getResponseAtomFeed();
		} else {
			throw new EditGridServiceException(res.getResponseText());
		}
	}
	
	/**
	 * Delete resource request.
	 * @param path Request path without the "rest/1/" prefix.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource deleted
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry delete(String path) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		return this.delete(path, new QueryOption());
	}
	
	/**
	 * Delete resource request with <code>QueryOption</code>.
	 * @param path Request path without the "rest/1/" prefix.
	 * @param option The query options of the request.
	 * @return An <code>AtomEntry</code> object contains metadata of the resource deleted
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public AtomEntry delete(String path, QueryOption option) 
			throws HttpException, IOException, SAXException, EditGridServiceException {
		URI u = new URI(this.baseUri, "rest/1/" + path, true);
		u.setQuery(option.toString());
		EditGridHttpRequest req = new EditGridHttpRequest("DELETE", u, this.appId);
		req.setApiToken(this.apiToken);
		EditGridHttpResponse res = req.getResponse();
		if (res.getStatusCode() == 200) {
			return res.getResponseAtomEntry();
		} else {
			throw new EditGridServiceException(res.getResponseText());
		}
	}
	
	/**
	 * Call EditGrid Action API.
	 * @param actionPath Action path.
	 * @param parameters Parameters passing to Action API service.
	 * @return
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public EditGridHttpResponse CallAction(String actionPath, HashMap<String, String> parameters) 
			throws HttpException, IOException, EditGridServiceException {
		URI u = new URI(this.baseUri, "rest/1/action/" + actionPath, true);
		QueryOption option = new QueryOption(parameters);
		u.setQuery(option.toString());
		EditGridHttpRequest req = new EditGridHttpRequest("GET", u, this.appId);
		req.setApiToken(this.apiToken);
		EditGridHttpResponse res = req.getResponse();
		if (res.getStatusCode() == 200) {
			return res;
		} else {
			throw new EditGridServiceException(res.getResponseText());
		}
	}
	
	/**
	 * Create a spreadsheet in the workspace specified by <code>workspacePath</code>.
	 * @param workspacePath The path of the destined workspace.
	 * @param spreadsheetName The name of the spreadsheet to be created.
	 * @param note The note of the spreadsheet to be created.
	 * @return A <code>Spreadsheet</code> object.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public Spreadsheet createSpreadsheet(String workspacePath, String spreadsheetName, String note)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		AtomEntry entry = new AtomEntry();
		entry.setValue("spreadsheet-name", spreadsheetName);
		entry.setValue("note", note);
		return new Spreadsheet(this, this.create("spreadsheets/" + workspacePath, entry));
	}
	
	/**
	 * Retrieve the spreadsheet specified by <code>spreadsheetPath</code>.
	 * @param spreadsheetPath Path of the spreadsheet to be retrieved.
	 * @return A <code>Spreadsheet</code> object.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public Spreadsheet getSpreadsheet(String spreadsheetPath)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		AtomEntry entry = this.queryEntry("spreadsheets/" + spreadsheetPath);
		return new Spreadsheet(this, entry);
	}
	
	/**
	 * Delete the spreadsheet specified by <code>spreadsheetPath</code>.
	 * @param spreadsheetPath Path of the spreadsheet to be deleted.
	 * @return A <code>Spreadsheet</code> object representing the spreadsheet deleted.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public Spreadsheet deleteSpreadsheet(String spreadsheetPath)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		AtomEntry entry = this.delete("spreadsheets/" + spreadsheetPath);
		return new Spreadsheet(this, entry);
	}
	
	/***
	 * Returns a list of spreadsheets in the workspace specified by <code>workspacePath</code>
	 * @param workspacePath Path of the workspace.
	 * @return A list of <code>Spreadsheet</code> objects.
	 * @throws HttpException If a protocol exception occurs.
	 * @throws IOException If an I/O (transport) error occurs.
	 * @throws SAXException
	 * @throws EditGridServiceException If EditGrid API 2.0 service returns an error.
	 */
	public List<Spreadsheet> listSpreadsheet(String workspacePath)
			throws HttpException, IOException, SAXException, EditGridServiceException {
		AtomFeed feed = this.query("spreadsheets/" +workspacePath);
		List<Spreadsheet> list = new ArrayList<Spreadsheet>();
		for (Iterator<AtomEntry> i = feed.getEntries().iterator(); i.hasNext();) {
			AtomEntry entry = i.next();
			list.add(new Spreadsheet(this, entry));
		}
		return list;
	}
	
	
}
